package application.mvc.sys.login;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;

import com.jfinal.kit.Prop;
import com.jfinal.kit.PropKit;
import com.jfinal.kit.StrKit;
import com.jfinal.log.Log;
import com.zkteco.biometric.FingerprintSensorErrorCode;
import com.zkteco.biometric.FingerprintSensorEx;
import application.common.model.User;
import application.common.render.HttpClientResult;
import application.common.ui.ProgressStage;
import application.common.util.FiltUtil;
import application.common.util.HttpUtil;
import application.common.util.ImgUtil;
import application.common.util.RouteUtil;
import application.common.util.ShiroUtil;
import application.common.util.StrUti;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.RotateTransition;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import net.sf.json.JSONObject;

public class LoginController implements Initializable {

	private final Log log = Log.getLog(getClass());

	@FXML
	private Label headerLabel;

	@FXML
	private Label serverPhoneLabel;

	@FXML
	private Label copyrightLabel;

	@FXML
	private Pane timerOutLinePane;

	@FXML
	private Pane timerInLinePane;

	@FXML
	private Circle fprCircle;

	@FXML
	private Button nextButton;

	@FXML
	private Label timerLabel;
	private Timeline animation;
	private RotateTransition rotateTransitionOut;
	private RotateTransition rotateTransitionIn;

	@FXML
	private Label messageLabel;
	private CascadeClassifier faceDetector;
	private CascadeClassifier eyeDetector;
	private List<User> users;
	private boolean isCompare = false;
	private Map<String, String> params = new HashMap<String, String>();

	private boolean isDestroy = false;
	private VideoCapture RGBCapture;
	private VideoCapture IRCapture;

	public void initialize(URL location, ResourceBundle resources) {
		isCompare = false;
		isDestroy = false;
		headerLabel.setText(ShiroUtil.getSoftName());
		serverPhoneLabel.setText(ShiroUtil.getPhone());
		copyrightLabel.setText(ShiroUtil.getCopyright());

		nextButton.setVisible(ShiroUtil.isDevMode());

		params.put("auth", ShiroUtil.getAuth());
		params.put("face", "");
		log.debug("激活登录验证引擎！");
		message("初始化引擎...", true);
		stopCamera = false;
		new Thread(new Task<Void>() {
			@Override
			protected Void call() throws Exception {
				users = User.dao.find("select * from " + User.TABLE_NAME);

				String facePath = String.format("%sresources/opencv/haarcascade_frontalface_alt.xml",
						FiltUtil.getPath());
				String eyePath = String.format("%sresources/opencv/haarcascade_eye.xml", FiltUtil.getPath());

				if (StrKit.isBlank(facePath) || StrKit.isBlank(eyePath)) {
					message("初始化引擎失败【请检查配置文件】...", false);
					return null;
				}
				faceDetector = new CascadeClassifier(facePath);
				eyeDetector = new CascadeClassifier(eyePath);
				if (faceDetector.empty() || eyeDetector.empty()) {
					message("初始化引擎失败【请检查配置文件】...", false);
					return null;
				}
				if (RGBCapture != null) {
					RGBCapture.release();
				}
				if (IRCapture != null) {
					IRCapture.release();
				}

				Prop p = PropKit.use(new File(String.format("%sresources/app-config-dev.txt", FiltUtil.getPath())));

				RGBCapture = new VideoCapture(p.getInt("RGB_CAMERA_INDEX", 0), 700);
				IRCapture = new VideoCapture(p.getInt("IR_CAMERA_INDEX", 1), 700);

				int RGBHeight = (int) RGBCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT);
				int RGBWidth = (int) RGBCapture.get(Videoio.CAP_PROP_FRAME_WIDTH);

				int IRHeight = (int) IRCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT);
				int IRWidth = (int) IRCapture.get(Videoio.CAP_PROP_FRAME_WIDTH);

				if (RGBHeight == 0 || RGBWidth == 0 || IRHeight == 0 || IRWidth == 0) {
					message("初始化引擎失败【请检查摄像头连接是否正常】...", false);
					return null;
				}
				if (!RGBCapture.isOpened() && !IRCapture.isOpened()) {
					message("初始化引擎失败【请检查摄像头连接是否正常】...", false);
					return null;
				}
				if(!isDestroy) initializeWebCam();
				if(isDestroy) {
					destroy();
				}
				return null;
			}
		}).start();
		initFpr();
		start();
	}

	public void exit(ActionEvent event) {
		if (!isDestroy) {
			isDestroy = true;
			ProgressStage.of(RouteUtil.mainStage, new Task<Void>() {
				@Override
				protected Void call() throws Exception {
					destroy();
					Platform.runLater(new Runnable() {
						public void run() {
							RouteUtil.goIndex();
						}
					});
					return null;
				}
			}, "返回首页中...").show();
		}
	}

	public void next(ActionEvent event) {
		if (!isDestroy) {
			isDestroy = true;
			User user = users.get(0);
			if (!StrKit.isBlank(user.getZp())) {
				Image image = new Image(new ByteArrayInputStream(Base64.getDecoder().decode(user.getZp())));
				user.setPortrait(image);
			} else {
				user.setPortrait(new Image(getClass().getResourceAsStream("/application/resources/aabb.jpeg")));
			}
			user.setRoleKey("setting|all");
			ShiroUtil.setUser(user);
			ProgressStage.of(RouteUtil.mainStage, new Task<Void>() {
				@Override
				protected Void call() throws Exception {
					destroy();
					Platform.runLater(new Runnable() {
						public void run() {
							RouteUtil.goArchives();
						}
					});
					return null;
				}
			}, "前往卷宗列表页面中...").show();
		}
	}

	private void message(final String text, final boolean isOk) {
		Platform.runLater(new Runnable() {
			public void run() {
				if (isOk) {
					messageLabel.setId("message-label");
				} else {
					messageLabel.setId("message-label-error");
				}
				messageLabel.setText(text);
				// log.debug(text);
			}
		});
	}

	private void start() {
		if(!isDestroy) {
			rotateTransitionOut = new RotateTransition(Duration.seconds(10), timerOutLinePane);
			rotateTransitionOut.setFromAngle(0);
			rotateTransitionOut.setToAngle(360);
			rotateTransitionOut.setByAngle(360); // 设置旋转的角度
			rotateTransitionOut.setCycleCount(Animation.INDEFINITE); // 设置旋转次数，我们需要旋转无数次
			rotateTransitionOut.setInterpolator(Interpolator.LINEAR);
			rotateTransitionOut.play();

			rotateTransitionIn = new RotateTransition(Duration.seconds(10), timerInLinePane);
			rotateTransitionIn.setFromAngle(360);
			rotateTransitionIn.setToAngle(0);
			rotateTransitionIn.setByAngle(360); // 设置旋转的角度
			rotateTransitionIn.setCycleCount(Animation.INDEFINITE); // 设置旋转次数，我们需要旋转无数次
			rotateTransitionIn.setInterpolator(Interpolator.LINEAR);
			rotateTransitionIn.play();
			startCountDown();
		}
	}

	public void startCountDown() {
		animation = new Timeline(new KeyFrame(Duration.millis(1000), new EventHandler<ActionEvent>() {
			public void handle(ActionEvent event) {
				if (RouteUtil.getTime() > 0) {
					RouteUtil.setTime(RouteUtil.getTime() - 1);
					if (RouteUtil.getTime() == 0) {
						timerLabel.setText("0");
						if (!isDestroy) {
							isDestroy = true;
							ProgressStage.of(RouteUtil.mainStage, new Task<Void>() {
								@Override
								protected Void call() throws Exception {
									destroy();
									RouteUtil.goIndex();
									return null;
								}
							}, "操作超时，自动退回主页...").show();
						}
					} else if (RouteUtil.getTime() > 0) {
						timerLabel.setText(RouteUtil.getTime() + "");
					}
				}

			}
		}));
		animation.setCycleCount(Timeline.INDEFINITE);
		animation.play();
	}

	public void destroy() {
		if (animation != null) {
			animation.stop();
		}
		if (rotateTransitionOut != null) {
			rotateTransitionOut.stop();
		}
		if (rotateTransitionIn != null) {
			rotateTransitionIn.stop();
		}
		try {
			disposeWebCamCamera();
		} catch (Exception e) {
		}
		try {
			freeSensor();
		} catch (Exception e) {
		}
	}

	/***********************************
	 * 人像识别
	 ********************************************/
	private boolean stopCamera = false;
	private ObjectProperty<Image> imageProperty = new SimpleObjectProperty<Image>();
	@FXML
	private ImageView imgWebCamCapturedImage;

	protected void initializeWebCam() {
		Task<Void> webCamTask = new Task<Void>() {
			protected Void call() throws Exception {
				message("正在初始化摄像头...", true);
				startWebCamStream();
				return null;
			}
		};

		Thread webCamThread = new Thread(webCamTask);
		webCamThread.setDaemon(true);
		webCamThread.start();
	}

	protected void disposeWebCamCamera() {
		stopCamera = true;
		if (RGBCapture != null) {
			try {
				RGBCapture.release();
			}catch (Exception e) {
			}
			
		}
		if (IRCapture != null) {
			try {
				IRCapture.release();
			}catch (Exception e) {
			}
		}
	}

	protected void startWebCamStream() {
		Task<Void> task = new Task<Void>() {
			protected Void call() throws Exception {
				final AtomicReference<WritableImage> ref0 = new AtomicReference<WritableImage>();
				Mat rgb = new Mat();
				Mat gray = new Mat();
				Mat IRRgb = new Mat();
				Mat IRGray = new Mat();
				while (!stopCamera) {
					try {
						RGBCapture.read(rgb);
						if (rgb.empty()) {
							continue;
						}

						IRCapture.read(IRRgb);
						if (!IRRgb.empty()) {
							Imgproc.cvtColor(IRRgb, IRGray, Imgproc.COLOR_RGB2GRAY);
							if (faceRecognition(IRRgb, IRGray)) {
								Imgproc.cvtColor(rgb, gray, Imgproc.COLOR_RGB2GRAY);
								if (faceRecognition(rgb, gray)) {
									if (!isCompare && !isDestroy) {
										isCompare = true;
										compare(rgb);
									}
								}
							}
						}
						ref0.set(SwingFXUtils.toFXImage(ImgUtil.mat2BufImg(rgb, ".bmp"), null));
						Platform.runLater(new Runnable() {
							public void run() {
								imageProperty.set(ref0.get());
							}
						});
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				if(isDestroy) {
					destroy();
				}
				return null;
			}
		};

		Thread th = new Thread(task);
		th.setDaemon(true);
		th.start();
		imgWebCamCapturedImage.imageProperty().bind(imageProperty);
		message("正在进行人像识别...", true);

	}

	/**
	 * OpenCV人眼识别
	 * 
	 * @param rgb
	 * @param gray
	 */
	public boolean eyeRecognition(Mat rgb, Mat gray) {
		MatOfRect rect = new MatOfRect();
		eyeDetector.detectMultiScale(gray, rect);
		if (rect.toArray().length >= 2) {
			return true;
		}
		return false;
	}

	/**
	 * OpenCV人脸识别
	 * 
	 * @param rgb
	 * @param gray
	 */
	public boolean faceRecognition(Mat rgb, Mat gray) {
		MatOfRect rect = new MatOfRect();
		faceDetector.detectMultiScale(gray, rect);
		if (rect.toArray().length >= 1) {
			if (eyeRecognition(rgb, gray)) {
				Rect maxRect = null;
				for (Rect re : rect.toArray()) {
					if (re.width >= 100 && re.height >= 100) {
						if (maxRect != null) {
							if (re.width > maxRect.width && re.height > maxRect.height) {
								maxRect = re;
							}
						} else {
							maxRect = re;
						}
					}
				}
				if (maxRect != null) {
					Imgproc.rectangle(rgb, new Point(maxRect.x, maxRect.y),
							new Point(maxRect.x + maxRect.width, maxRect.y + maxRect.height), new Scalar(50, 205, 50),
							2);
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * 与库中人脸匹配
	 * 
	 * @return
	 */
	public void compare(final Mat mat_1) {
		new Thread(new Task<Void>() {
			@Override
			protected Void call() throws Exception {
				message("识别到人脸，正在进行对比...", true);
				try {
					String face=ImgUtil.matToBase64(mat_1);
					if(StrKit.isBlank(face)) {
						message("正在识别人脸...", true);
						isCompare = false;
						return null;
					}
					params.put("face", face);
					HttpClientResult httpClientResult=HttpUtil.doPost(String.format("%s/api/cab/verifyFace", ShiroUtil.getConfig().getSynURL()), params);
					if (httpClientResult == null) {
						message("正在识别人脸...", true);
						isCompare = false;
						return null;
					}
					if (httpClientResult.getCode() != 200) {
						message("正在识别人脸...", true);
						isCompare = false;
						return null;
					}
					String content = httpClientResult.getContent();
					if (StrKit.isBlank(content)) {
						message("正在识别人脸...", true);
						isCompare = false;
						return null;
					}
					JSONObject jsonObject = JSONObject.fromObject(content);
					if (jsonObject == null) {
						message("正在识别人脸...", true);
						isCompare = false;
						return null;
					}

					if (jsonObject.has("code")) {
						int code = jsonObject.getInt("code");
						if (code != 200) {
							message("正在识别人脸...", true);
							isCompare = false;
							return null;
						}
						if (jsonObject.has("data")) {
							String data = jsonObject.getString("data");
							if (StrKit.isBlank(data)) {
								message("正在识别人脸...", true);
								isCompare = false;
								return null;
							}
							User user = User.dao.findById(data);
							if (user != null && !isDestroy) {
								isDestroy = true;
								Platform.runLater(new Runnable() {
									public void run() {
										ProgressStage.of(RouteUtil.mainStage, new Task<Void>() {
											@Override
											protected Void call() throws Exception {
												log.info("指纹比对成功，前往卷宗列表页面中...");
												if (!StrKit.isBlank(user.getZp())) {
													Image image = new Image(new ByteArrayInputStream(
															Base64.getDecoder().decode(user.getZp())));
													user.setPortrait(image);
												} else {
													user.setPortrait(new Image(getClass()
															.getResourceAsStream("/application/resources/aabb.jpeg")));
												}
												ShiroUtil.setUser(user);
												destroy();
												RouteUtil.goArchives();
												return null;
											}
										}, "人脸识别成功，前往卷宗列表页面中...").show();
									}
								});
								isCompare = true;
								return null;
							} else {
								message("正在识别人脸...", true);
								isCompare = false;
								return null;
							}
						}
					}
				} catch (Exception e) {
				}
				message("正在识别人脸...", true);
				isCompare = false;
				return null;
			}
		}).start();
	}

	/************************************************
	 * 指纹识别
	 ********************************************************/
	int fpWidth = 0;
	int fpHeight = 0;
	static final int enroll_cnt = 3;
	private byte[] imgbuf = null;
	private byte[] template = new byte[2048];
	private int[] templateLen = new int[1];
	private boolean mbStop = true;
	private long mhDevice = 0;
	private long mhDB = 0;
	private WorkThread workThread = null;

	private void initFpr() {

		if (0 != mhDevice) {
			log.info("指纹仪已占用!");
			return;
		}
		int ret = FingerprintSensorErrorCode.ZKFP_ERR_OK;
		// Initialize
		if (FingerprintSensorErrorCode.ZKFP_ERR_OK != FingerprintSensorEx.Init()) {
			log.info("Init failed!");
			log.info("指纹仪初始化失败!");
			return;
		}
		ret = FingerprintSensorEx.GetDeviceCount();
		if (ret < 0) {
			log.info("指纹仪连接异常!");
			freeSensor();
			return;
		}
		if (0 == (mhDevice = FingerprintSensorEx.OpenDevice(0))) {
			log.info(String.format("指纹仪开启失败，错误代码【%s】!", ret));
			freeSensor();
			return;
		}
		if (0 == (mhDB = FingerprintSensorEx.DBInit())) {
			log.info(String.format("指纹仪算法库初始化失败，错误代码【%s】!", ret));
			freeSensor();
			return;
		}

		byte[] paramValue = new byte[4];
		int[] size = new int[1];
		size[0] = 4;
		FingerprintSensorEx.GetParameters(mhDevice, 1, paramValue, size);
		fpWidth = byteArrayToInt(paramValue);
		size[0] = 4;
		FingerprintSensorEx.GetParameters(mhDevice, 2, paramValue, size);
		fpHeight = byteArrayToInt(paramValue);
		imgbuf = new byte[fpWidth * fpHeight];
		mbStop = false;

		Platform.runLater(new Runnable() {
			public void run() {
				fprCircle.setId("login-fpr-succ-circle");
			}
		});
		log.info("指纹仪开启成功！");
		workThread = new WorkThread();
		workThread.start();
	}

	private static int byteArrayToInt(byte[] bytes) {
		int number = bytes[0] & 0xFF;
		number |= ((bytes[1] << 8) & 0xFF00);
		number |= ((bytes[2] << 16) & 0xFF0000);
		number |= ((bytes[3] << 24) & 0xFF000000);
		return number;
	}

	private void freeSensor() {
		try {
			mbStop = true;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (0 != mhDB) {
				FingerprintSensorEx.DBFree(mhDB);
				mhDB = 0;
			}
			if (0 != mhDevice) {
				FingerprintSensorEx.CloseDevice(mhDevice);
				mhDevice = 0;
			}
			FingerprintSensorEx.Terminate();
		} catch (Exception e) {
		}
	}

	private class WorkThread extends Thread {
		@Override
		public void run() {
			super.run();
			int ret = 0;
			while (!mbStop) {
				templateLen[0] = 2048;
				if (0 == (ret = FingerprintSensorEx.AcquireFingerprint(mhDevice, imgbuf, template, templateLen))) {
					if (users != null && users.size() > 0) {
						for (User user : users) {
							if (!StrKit.isBlank(user.getFpr1()) && !StrKit.equals("null", user.getFpr1())) {
								try {
									ret = FingerprintSensorEx.DBMatch(mhDevice, StrUti.base64Decode(user.getFpr1()),
											template);
									if (ret > 0) {
										OnDBMatchOK(true, user);
										break;
									}
								} catch (Exception e) {
									log.error("指纹异常", e);
								}
							}
							
							if(!StrKit.isBlank(user.getFpr2()) && !StrKit.equals("null", user.getFpr2())){
								try {
									ret = FingerprintSensorEx.DBMatch(mhDevice, StrUti.base64Decode(user.getFpr2()),
											template);
									if (ret > 0) {
										OnDBMatchOK(true, user);
										break;
									}
								} catch (Exception e) {
									log.error("指纹异常", e);
								}
							}
						}
					}
				}
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
				}
			}

		}
	}

	private void OnDBMatchOK(boolean isDBMatch, User user) {
		if (isDBMatch && !isDestroy) {
			isDestroy = true;
			Platform.runLater(new Runnable() {
				public void run() {
					ProgressStage.of(RouteUtil.mainStage, new Task<Void>() {
						@Override
						protected Void call() throws Exception {
							destroy();
							log.info("指纹比对成功，前往卷宗列表页面中...");
							if (!StrKit.isBlank(user.getZp())) {
								Image image = new Image(
										new ByteArrayInputStream(Base64.getDecoder().decode(user.getZp())));
								user.setPortrait(image);
							} else {
								user.setPortrait(
										new Image(getClass().getResourceAsStream("/application/resources/aabb.jpeg")));
							}
							ShiroUtil.setUser(user);
							RouteUtil.goArchives();
							return null;
						}
					}, "指纹比对成功，前往卷宗列表页面中...").show();
				}
			});
		}
	}
}
